# X:[[1.227e+01 2.997e+01 7.742e+01 ... 0.000e+00 2.409e-01 6.743e-02]
#    [1.218e+01 2.052e+01 7.722e+01 ... 7.431e-02 2.694e-01 6.878e-02]]
# Y:[1 1 0 0 0 1 1 0 1 0 0 0 1 1 0 1 0 0 1 1 1 1 1 0 0 0 1 0 1 1 1 0 0 1 0 1 0]
#

import numpy as np
import math
from sklearn.datasets import load_breast_cancer
from sklearn.preprocessing import scale
from random import random
from sklearn.model_selection import train_test_split


class GDA(object):
    X = np.array([])
    Y = np.array([])
    data_num = 0
    feature_size = 0
    Ppos = 0
    Pneg = 0
    Ave_pos = np.array([])
    Ave_neg = np.array([])
    Var_pos = np.array([])
    Var_neg = np.array([])
    Cov_pos = np.array([])
    Cov_neg = np.array([])

    def __init__(self, train_data, train_target):
        self.X = train_data
        self.Y = train_target
        self.data_num = train_data.shape[0]
        self.feature_size = train_data.shape[1]
        self.Ppos = sum(train_target) / self.data_num
        self.Pneg = 1 - self.Ppos
        posSum = np.zeros((self.feature_size,))
        negSum = np.zeros((self.feature_size,))
        posData = np.array([])
        negData = np.array([])
        for i in range(self.data_num):
            if train_target[i] == 1:
                posSum += train_data[i]
                if posData.size == 0:
                    posData = train_data[i]
                else:
                    posData = np.vstack((posData, train_data[i]))
            else:
                negSum += train_data[i]
                if negData.size == 0:
                    negData = train_data[i]
                else:
                    negData = np.vstack((negData, train_data[i]))
        self.Ave_pos = posSum / sum(train_target)
        self.Ave_neg = negSum / (self.data_num - sum(train_target))
        self.Var_pos = np.zeros((self.feature_size,))
        self.Var_neg = np.zeros((self.feature_size,))
        for i in range(self.data_num):
            self.Var_pos += (train_data[i] - self.Ave_pos) ** 2
            self.Var_neg += (train_data[i] - self.Ave_neg) ** 2
        self.Var_pos = self.Var_pos / sum(train_target)
        self.Var_neg = self.Var_neg / (self.data_num - sum(train_target))
        self.Cov_pos = np.cov(posData.T)
        self.Cov_neg = np.cov(negData.T)

    def predict(self, test_data):
        predict_target = []
        Cov_pos_det = np.sqrt(np.linalg.det(self.Cov_pos))
        Cov_neg_det = np.sqrt(np.linalg.det(self.Cov_neg))
        Cov_pos_inv = np.linalg.inv(self.Cov_pos)
        Cov_neg_inv = np.linalg.inv(self.Cov_neg)
        for i in range(test_data.shape[0]):
            tmp1 = math.pow((2 * np.pi), test_data.shape[0] / 2)  # *np.sqrt(Cov_pos_det)
            tmp_pos = tmp1 * Cov_pos_det
            tmp_neg = tmp1 * Cov_neg_det
            tmp_pos_exp = -np.dot(np.dot((test_data[i] - self.Ave_pos), Cov_pos_inv),
                                  ((test_data[i] - self.Ave_pos).T)) / 2
            tmp_neg_exp = -np.dot(np.dot((test_data[i] - self.Ave_neg), Cov_neg_inv),
                                  ((test_data[i] - self.Ave_neg).T)) / 2
            P_X_Cpos = np.exp(tmp_pos_exp) / tmp_pos
            P_X_Cneg = np.exp(tmp_neg_exp) / tmp_neg
            tmp_target = self.Ppos * P_X_Cpos / (P_X_Cpos + P_X_Cneg)
            if tmp_target >= 0.5:
                predict_target.append(1)
            else:
                predict_target.append(0)
        return predict_target

    def evaluate(self, predict_target, test_target):
        err = 0
        for i in range(len(predict_target)):
            if predict_target[i] != test_target[i]:
                err += 1
        return err / len(predict_target)


if __name__ == "__main__":
    cancer = load_breast_cancer()
    train_data, test_data, train_target, test_target = train_test_split(cancer.data, \
                                                                        cancer.target, \
                                                                        test_size=0.1, \
                                                                        random_state=0)

    gda = GDA(train_data, train_target)
    predict = gda.predict(test_data)
    print('准确率：', 1 - gda.evaluate(predict, test_target))
